/*
 * Copyright (c) 2019-2020, Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#include <ztest.h>
#include <drivers/clock_control.h>
#include <drivers/clock_control/nrf_clock_control.h>
#include <hal/nrf_clock.h>
#include <drivers/sensor.h>
#include <logging/log.h>
LOG_MODULE_REGISTER(test);

#ifndef CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC
#error "LFCLK must use RC source"
#endif

#define CALIBRATION_PROCESS_TIME_MS 35

extern void mock_temp_nrf5_value_set(struct sensor_value *val);

static void turn_on_clock(struct device *dev, clock_control_subsys_t subsys)
{
	clock_control_on(dev, subsys);
	while (clock_control_get_status(dev, subsys) !=
		CLOCK_CONTROL_STATUS_ON) {

	}
}

static void turn_off_clock(struct device *dev, clock_control_subsys_t subsys)
{
	int err;

	do {
		err = clock_control_off(dev, subsys);
	} while (err == 0);
}

#define TEST_CALIBRATION(exp_cal, exp_skip, sleep_ms) \
	test_calibration(exp_cal, exp_skip, sleep_ms, __LINE__)

/* Function tests if during given time expected number of calibrations and
 * skips occurs.
 */
static void test_calibration(uint32_t exp_cal, uint32_t exp_skip,
				uint32_t sleep_ms, uint32_t line)
{
	int cal_cnt;
	int skip_cnt;

	cal_cnt = z_nrf_clock_calibration_count();
	skip_cnt = z_nrf_clock_calibration_skips_count();

	k_sleep(K_MSEC(sleep_ms));

	cal_cnt = z_nrf_clock_calibration_count() - cal_cnt;
	skip_cnt = z_nrf_clock_calibration_skips_count() - skip_cnt;

	zassert_equal(cal_cnt, exp_cal,
			"%d: Unexpected number of calibrations (%d, exp:%d)",
			line, cal_cnt, exp_cal);
	zassert_equal(skip_cnt, exp_skip,
			"%d: Unexpected number of skips (%d, exp:%d)",
			line, skip_cnt, exp_skip);
}

/* Function pends until calibration counter is performed. When function leaves,
 * it is just after calibration.
 */
static void sync_just_after_calibration(void)
{
	uint32_t cal_cnt = z_nrf_clock_calibration_count();

	/* wait until calibration is performed. */
	while (z_nrf_clock_calibration_count() == cal_cnt) {
		k_sleep(K_MSEC(1));
	}
}

/* Test checks if calibration and calibration skips are performed according
 * to timing configuration.
 */
static void test_basic_clock_calibration(void)
{
	int wait_ms = CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD *
		(CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP + 1) +
		CALIBRATION_PROCESS_TIME_MS;
	struct sensor_value value = { .val1 = 0, .val2 = 0 };

	mock_temp_nrf5_value_set(&value);
	sync_just_after_calibration();

	TEST_CALIBRATION(1, CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP,
			 wait_ms);
}

/* Test checks if calibration happens just after clock is enabled. */
static void test_calibration_after_enabling_lfclk(void)
{
	struct device *clk_dev =
		device_get_binding(DT_LABEL(DT_INST(0, nordic_nrf_clock)));
	struct sensor_value value = { .val1 = 0, .val2 = 0 };

	mock_temp_nrf5_value_set(&value);

	turn_off_clock(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF);

	k_busy_wait(10000);

	turn_on_clock(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF);

	TEST_CALIBRATION(1, 0,
			 CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD);
}

/* Test checks if temperature change triggers calibration. */
static void test_temp_change_triggers_calibration(void)
{
	struct sensor_value value = { .val1 = 0, .val2 = 0 };

	mock_temp_nrf5_value_set(&value);
	sync_just_after_calibration();

	/* change temperature by 0.25'C which should not trigger calibration */
	value.val2 += ((CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_TEMP_DIFF - 1) *
			250000);
	mock_temp_nrf5_value_set(&value);

	/* expected one skip */
	TEST_CALIBRATION(0, CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP,
				CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP *
				CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD +
				CALIBRATION_PROCESS_TIME_MS);

	TEST_CALIBRATION(1, 0,
			 CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD + 40);

	value.val2 += (CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_TEMP_DIFF * 250000);
	mock_temp_nrf5_value_set(&value);

	/* expect calibration due to temp change. */
	TEST_CALIBRATION(1, 0,
			 CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD + 40);
}

/* Test checks if z_nrf_clock_calibration_force_start() results in immediate
 * calibration.
 */
static void test_force_calibration(void)
{
	sync_just_after_calibration();

	z_nrf_clock_calibration_force_start();

	/*expect immediate calibration */
	TEST_CALIBRATION(1, 0,
		CALIBRATION_PROCESS_TIME_MS + 5);

	/* and back to scheduled operation. */
	TEST_CALIBRATION(1, CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP,
		CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD *
		(CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP + 1) +
		CALIBRATION_PROCESS_TIME_MS);

}

void test_main(void)
{
	ztest_test_suite(test_nrf_clock_calibration,
		ztest_unit_test(test_basic_clock_calibration),
		ztest_unit_test(test_calibration_after_enabling_lfclk),
		ztest_unit_test(test_temp_change_triggers_calibration),
		ztest_unit_test(test_force_calibration)
			 );
	ztest_run_test_suite(test_nrf_clock_calibration);
}
